﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Text;
using System.Data.Objects;
using Slam.Configuration;
using Slam;
using Microsoft.SharePoint;
using System.Web;
using System.Data.EntityClient;
using SlamCms.SharePoint.Tagging.Data;
using SlamCms.SharePoint.Data;
using Microsoft.SharePoint.Client.Services;
using Microsoft.SharePoint.WebControls;
using SlamCms.SharePoint.Tagging.WebParts;
using SlamCms.SharePoint.Core;

namespace SlamCms.SharePoint.Tagging
{
	[ServiceContract(Namespace = "")]
	[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
	[BasicHttpBindingServiceMetadataExchangeEndpoint]
	[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
	public class TagTreeManagerService
	{
		private const string ModelName = "Data.SlamCms";
		private string _connectionString;
		private string _dataSchema;

		private void LoadConnectionConfiguration()
		{
			IConfigurationManager slamConfiguration = ComponentService.GetInstance<IConfigurationManager>();
			DataMapping currentDataMapping = slamConfiguration.DataMapping;
			string siteCollection = SPContext.GetContext(HttpContext.Current).Site.ServerRelativeUrl.TrimStart('/');
			if (!String.IsNullOrEmpty(siteCollection) && slamConfiguration.DataMappings.ContainsKey(siteCollection))
			{
				currentDataMapping = slamConfiguration.DataMappings[siteCollection];
			}
			if (slamConfiguration is XmlConfigurationManager)
			{

				_connectionString = ((XmlSqlDataMapping)currentDataMapping).GetConnectionString();
				_dataSchema = ((XmlSqlDataMapping)currentDataMapping).GetDataSchema();
			}

			if (String.IsNullOrEmpty(_connectionString))
			{
				_connectionString = slamConfiguration.GetSection<ConnectionStringSection>("ConnectionStrings").Default;
			}
			else
			{
				_connectionString = slamConfiguration.GetSection<ConnectionStringSection>("ConnectionStrings").GetConnectionString(_connectionString);
			}

			EntityConnectionStringBuilder entityConnection = new EntityConnectionStringBuilder();
			entityConnection.Provider = "System.Data.SqlClient";
			if (!_connectionString.Contains("Persist Security Info"))
			{
				_connectionString = _connectionString.TrimEnd(';') + ";Persist Security Info=True;";
			}
			entityConnection.ProviderConnectionString = _connectionString;
			entityConnection.Metadata = String.Format("res://*/{0}.csdl|res://*/{0}.ssdl|res://*/{0}.msl", ModelName);

			_connectionString = entityConnection.ToString();
		}

		private string ConnectionString
		{
			get
			{
				if (String.IsNullOrEmpty(_connectionString))
				{
					LoadConnectionConfiguration();	
				}
				return _connectionString;
			}
		}

		private string DataSchema
		{
			get
			{
				if (String.IsNullOrEmpty(_dataSchema))
				{
					LoadConnectionConfiguration();
				}
				return _dataSchema;
			}
		}

		[WebInvoke(Method = "GET",
		ResponseFormat = WebMessageFormat.Json,
		UriTemplate = "DeleteTag?key={key}",
		BodyStyle = WebMessageBodyStyle.Bare)]
		[OperationContract]
		public void DeleteTag(string key)
		{
			SPWeb site = SPControl.GetContextWeb(HttpContext.Current);
			SPList tagList = site.Lists["Tags"];
			if (tagList.DoesUserHavePermissions(SPBasePermissions.DeleteListItems))
			{
				string[] keyParts = key.Split('|');
				string tagId = "";
				int listRelativeId = 0;

				if (keyParts.Length == 4)
				{
					tagId = keyParts[2];
					listRelativeId = Int32.Parse(keyParts[0]);
				}

				Entities context = new Entities(ConnectionString);
				context.SetStoreSchema(ModelName, DataSchema);

				var hierarchyPositions = context.TagHierarchyPositions.Where(p => p.Tag != null && p.Tag.TagID == tagId);
				foreach (var hierarchyPosition in hierarchyPositions)
				{
					DeleteTagHierarchyPosition(hierarchyPosition.PositionID);
				}

				try
				{
					context.ExecuteStoreCommand(String.Format("delete from Tag where TagID = '{0}'", tagId));
				}
				catch { }

				site.AllowUnsafeUpdates = true;
				tagList.Items.DeleteItemById(listRelativeId);
			}
		}

		private void DeleteTagHierarchyPosition(int positionId)
		{
			Entities context = new Entities(ConnectionString);
			context.SetStoreSchema(ModelName, DataSchema);

			var childPositions = context.TagHierarchyPositions.Where(p => p.ParentPositionID == positionId);
			foreach (var childPosition in childPositions)
			{
				DeleteTagHierarchyPosition(childPosition.PositionID);
			}

			var position = context.TagHierarchyPositions.Where(p => p.PositionID == positionId).FirstOrDefault();
			context.DeleteObject(position);
			context.SaveChanges(true);
		}

		[WebInvoke(Method = "GET",
		ResponseFormat = WebMessageFormat.Json,
		UriTemplate = "DeleteTagCategory?key={key}",
		BodyStyle = WebMessageBodyStyle.Bare)]
		[OperationContract]
		public void DeleteTagCategory(string key)
		{
			SPWeb site = SPControl.GetContextWeb(HttpContext.Current);
			SPList tagCategoryList = site.Lists["Tag Categories"];
			if (tagCategoryList.DoesUserHavePermissions(SPBasePermissions.DeleteListItems))
			{
				string[] keyParts = key.Split('|');
				string tagCategoryId = "";
				int listRelativeId = 0;

				if (keyParts.Length == 2)
				{
					tagCategoryId = keyParts[1];
					listRelativeId = Int32.Parse(keyParts[0]);
				}

				Entities context = new Entities(ConnectionString);
				context.SetStoreSchema(ModelName, DataSchema);

				var hierarchyPositions = context.TagHierarchyPositions.Where(p => p.TagCategory.TagCategoryID == tagCategoryId);
				foreach (var hierarchyPosition in hierarchyPositions)
				{
					context.DeleteObject(hierarchyPosition);
				}
				context.SaveChanges(true);

				var tagCategory = context.TagCategories.Where(t => t.TagCategoryID == tagCategoryId).FirstOrDefault();
				context.DeleteObject(tagCategory);
				context.SaveChanges(true);


				site.AllowUnsafeUpdates = true;
				tagCategoryList.Items.DeleteItemById(listRelativeId);
			}
		}

		[WebInvoke(Method = "GET",
		ResponseFormat = WebMessageFormat.Json,
		UriTemplate = "GetTagTree?tagTreeId={tagTreeId}",
		BodyStyle = WebMessageBodyStyle.Bare)]
		[OperationContract]
		public List<DynaTreeNode> GetTagTree(string tagTreeId)
		{
			Entities context = new Entities(ConnectionString);
			context.SetStoreSchema(ModelName, DataSchema);

			var categories = (from cat in context.TagCategories.AsEnumerable()
							  orderby cat.DisplayOrder
							  select new DynaTreeNode
							  {
								  title = cat.Title,
								  key = cat.ListRelativeID.ToString() + "|" + cat.TagCategoryID,
								  isFolder = true,
								  expand = false,
								  hideCheckBox = true
							  });

			List<DynaTreeNode> treeNodes = categories.ToList();

			_allTagHierarchyPositions = (from tag in context.TagHierarchyPositions.Include("Tag").Include("TagCategory")
										 where tag.TagTree.TagTreeID == tagTreeId
										 select tag).ToList();

			foreach (DynaTreeNode node in treeNodes)
			{
				node.children = GetChildren(null, node.key.Split('|')[1]).ToList();
			}

			return treeNodes;
		}

		private List<TagHierarchyPosition> _allTagHierarchyPositions;

		private List<DynaTreeNode> GetChildren(int? parentId, string categoryId)
		{
			List<DynaTreeNode> childNodes = (from t in _allTagHierarchyPositions
											 where t.Tag != null && t.ParentPositionID == parentId && t.TagCategory.TagCategoryID == categoryId
											 orderby t.SortOrder
											 select new DynaTreeNode
											 {
												 title = String.IsNullOrEmpty(t.Tag.DisplayName) ? t.Tag.Identifier : t.Tag.DisplayName,
												 key = t.Tag.ListRelativeID.ToString() + "|" + t.PositionID.ToString() + "|" + t.Tag.TagID + "|" + t.TagCategory.TagCategoryID,
												 isFolder = false,
												 expand = false,
												 hideCheckBox = true
											 }).ToList();

			foreach (var childNode in childNodes)
			{
				string[] keyParts = childNode.key.Split('|');
				childNode.children = GetChildren(Int32.Parse(keyParts[1]), keyParts[3]).ToList();
			}
			return childNodes;
		}

		[WebInvoke(Method = "GET",
		ResponseFormat = WebMessageFormat.Json,
		UriTemplate = "GetNewTreeNodeForExistingTag?parentKey={parentKey}&tagName={tagName}&tagTreeId={tagTreeId}",
		BodyStyle = WebMessageBodyStyle.Bare)]
		[OperationContract]
		public DynaTreeNode GetNewTreeNodeForExistingTag(string parentKey, string tagName, string tagTreeId)
		{
			string tagIdentifier = tagName;
			if (tagName.LastIndexOf(")") > -1)
			{
				string partialTagName = tagName.Substring(0, tagName.LastIndexOf(")"));
				tagIdentifier = partialTagName.Substring(partialTagName.LastIndexOf("(") + 1, partialTagName.Length - partialTagName.LastIndexOf("(") - 1);
			}
			string[] keyParts = parentKey.Split('|');
			string tagCategoryId = "";
			int? parentPositionId = null;
			string tagId = "";

			if (keyParts.Length == 2) //Parent is TagCategory
			{
				tagCategoryId = keyParts[1];
			}
			else if (keyParts.Length == 4) //Parent is Tag
			{
				tagCategoryId = keyParts[3];
				parentPositionId = Int32.Parse(keyParts[1]);
			}

			Entities context = new Entities(ConnectionString);
			context.SetStoreSchema(ModelName, DataSchema);

			DynaTreeNode node = new DynaTreeNode();
			Tag matchedTag = context.Tags.Where(t => t.Identifier.Equals(tagIdentifier, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
			TagCategory matchedTagCategory = context.TagCategories.Where(tc => tc.TagCategoryID.Equals(tagCategoryId, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
			TagTree matchedTagTree = context.TagTrees.Where(tt => tt.TagTreeID.Equals(tagTreeId, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
			if (matchedTag != null && !String.IsNullOrEmpty(matchedTag.TagID))
			{
				tagId = matchedTag.TagID;

				TagHierarchyPosition position = new TagHierarchyPosition();
				position.ParentPositionID = parentPositionId;
				position.Tag = matchedTag;
				position.TagCategory = matchedTagCategory;
				position.SortOrder = 0;
				position.TagTree = matchedTagTree;
				context.AddToTagHierarchyPositions(position);
				context.SaveChanges(true);

				node = (from tag in context.TagHierarchyPositions.Include("Tag").AsEnumerable()
						where tag.Tag != null && tag.PositionID == position.PositionID
						select new DynaTreeNode
						{
							title = String.IsNullOrEmpty(tag.Tag.DisplayName) ? tag.Tag.Identifier : tag.Tag.DisplayName,
							key = tag.Tag.ListRelativeID.ToString() + "|" + tag.PositionID.ToString() + "|" + tag.Tag.TagID + "|" + tag.TagCategory.TagCategoryID,
							isFolder = false,
							expand = false,
							hideCheckBox = true
						}).FirstOrDefault();

				SlamCmsContext.Data.Cache.InvalidateTags();
			}

			return node;
		}

		private List<int> _movePositions = new List<int>();

		[WebInvoke(Method = "GET",
		ResponseFormat = WebMessageFormat.Json,
		UriTemplate = "MoveTagHierarchyPosition?key={key}&target={targetKey}&mode={mode}&tagTreeId={tagTreeId}",
		BodyStyle = WebMessageBodyStyle.Bare)]
		[OperationContract]
		public void MoveTagHierarchyPosition(string key, string targetKey, string mode, string tagTreeId)
		{
			Entities context = new Entities(ConnectionString);
			context.SetStoreSchema(ModelName, DataSchema);

			string[] keyParts = key.Split('|');
			int positionId = 0;
			TagHierarchyPosition position = null;

			if (keyParts.Length == 4)
			{
				positionId = Int32.Parse(keyParts[1]);
				position = context.TagHierarchyPositions.Include("TagCategory").Where(p => p.PositionID == positionId).FirstOrDefault();
			}

			string[] targetKeyParts = targetKey.Split('|');
			int targetPositionId = 0;
			TagHierarchyPosition targetPosition = null;
			string tagCategoryId = "";

			if (targetKeyParts.Length == 4) //Target is Tag
			{
				targetPositionId = Int32.Parse(targetKeyParts[1]);
				targetPosition = context.TagHierarchyPositions.Include("TagCategory").Where(p => p.PositionID == targetPositionId).FirstOrDefault();

				tagCategoryId = targetPosition.TagCategory.TagCategoryID;
			}
			else if (targetKeyParts.Length == 2) //Target is Tag Category
			{
				tagCategoryId = targetKeyParts[1];
			}

			int? parentPositionId = null;

			if (targetPosition != null && mode.Equals("over", StringComparison.OrdinalIgnoreCase))
			{	
				parentPositionId = targetPosition.PositionID;
			}
			else if (targetPosition != null && mode.Equals("before", StringComparison.OrdinalIgnoreCase))
			{	
				parentPositionId = targetPosition.ParentPositionID;
			}
			else if(targetPosition != null)
			{	
				parentPositionId = targetPosition.ParentPositionID;
			}

			StorePositionsForTagHierarchyPositionsForMove(position.PositionID);
			position.ParentPositionID = parentPositionId;

			TagCategory matchedTagCategory = context.TagCategories.Where(tc => tc.TagCategoryID.Equals(tagCategoryId, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();

			var movedHierarchyPositions = (from hierarchyPosition in context.TagHierarchyPositions
										   where hierarchyPosition.TagTree.TagTreeID == tagTreeId
										   select hierarchyPosition);

			foreach (var hierarchyPosition in movedHierarchyPositions)
			{
				if (_movePositions.Contains(hierarchyPosition.PositionID))
				{	
					hierarchyPosition.TagCategory = matchedTagCategory;
				}
			}
			context.SaveChanges(true);
		}

		private void StorePositionsForTagHierarchyPositionsForMove(int positionId)
		{
			Entities context = new Entities(ConnectionString);
			context.SetStoreSchema(ModelName, DataSchema);

			var position = context.TagHierarchyPositions.Where(p => p.PositionID == positionId).FirstOrDefault();
			_movePositions.Add(positionId);

			var childPositions = context.TagHierarchyPositions.Where(p => p.ParentPositionID == positionId);
			foreach (var childPosition in childPositions)
			{
				StorePositionsForTagHierarchyPositionsForMove(childPosition.PositionID);
			}
		}

		[WebInvoke(Method = "GET",
		ResponseFormat = WebMessageFormat.Json,
		UriTemplate = "RemoveTagHierarchyPosition?key={key}",
		BodyStyle = WebMessageBodyStyle.Bare)]
		[OperationContract]
		public void RemoveTagHierarchyPosition(string key)
		{
			string[] keyParts = key.Split('|');
			int positionId = 0;

			if (keyParts.Length == 4)
			{	
				positionId = Int32.Parse(keyParts[1]);
			}

			if (positionId != 0)
			{
				DeleteTagHierarchyPosition(positionId);
				SlamCmsContext.Data.Cache.InvalidateTags();
			}
		}

		[WebInvoke(Method = "POST",
		RequestFormat = WebMessageFormat.Json,
		ResponseFormat = WebMessageFormat.Json,
		UriTemplate = "ReorderTagCategories",
		BodyStyle = WebMessageBodyStyle.WrappedRequest)]
		[OperationContract]
		public void ReorderTagCategories(string orderedTagCategoryKeys)
		{
			Dictionary<string, int> tagCategorySortOrderValues = new Dictionary<string, int>();
			int i = 1;
			string[] tagCategoryNodes = orderedTagCategoryKeys.Split(',');
			foreach (string tagCategoryNode in tagCategoryNodes)
			{
				tagCategorySortOrderValues.Add(tagCategoryNode.Split('|')[1], i);
				i++;
			}

			Entities context = new Entities(ConnectionString);
			context.SetStoreSchema(ModelName, DataSchema);

			var tagCategories = context.TagCategories;
			foreach (var tagCategory in tagCategories)
			{
				if (tagCategorySortOrderValues.ContainsKey(tagCategory.TagCategoryID))
				{
					tagCategory.DisplayOrder = tagCategorySortOrderValues[tagCategory.TagCategoryID];
				}
			}
			context.SaveChanges(true);

			SPWeb site = SPControl.GetContextWeb(HttpContext.Current);
			SPList tagCategoryList = site.Lists["Tag Categories"];
			site.AllowUnsafeUpdates = true;

			foreach (TagCategory category in context.TagCategories)
			{
				SPListItem categoryListItem = tagCategoryList.Items.GetItemById(category.ListRelativeID);
				categoryListItem["Display Order"] = category.DisplayOrder;
				categoryListItem.SystemUpdate(false);
			}
			SlamCmsContext.Data.Cache.InvalidateTags();
		}

		[WebInvoke(Method = "POST",
		RequestFormat = WebMessageFormat.Json,
		ResponseFormat = WebMessageFormat.Json,
		UriTemplate = "ReorderTagHierarchyPositions",
		BodyStyle = WebMessageBodyStyle.WrappedRequest)]
		[OperationContract]
		public void ReorderTagHierarchyPositions(string orderedTagKeys, string tagTreeId)
		{
			Dictionary<int, int> tagSortOrderValues = new Dictionary<int, int>();
			int i = 1;
			string[] tagNodes = orderedTagKeys.Split(',');
			foreach (string tagNode in tagNodes)
			{
				tagSortOrderValues.Add(Int32.Parse(tagNode.Split('|')[1]), i);
				i++;
			}

			Entities context = new Entities(ConnectionString);
			context.SetStoreSchema(ModelName, DataSchema);

			var tagHierarchyPositions = context.TagHierarchyPositions.Where(p => p.TagTree.TagTreeID == tagTreeId);
			foreach (var tagHierarchyPostion in tagHierarchyPositions)
			{
				if (tagSortOrderValues.ContainsKey(tagHierarchyPostion.PositionID))
				{
					tagHierarchyPostion.SortOrder = tagSortOrderValues[tagHierarchyPostion.PositionID];
				}
			}
			context.SaveChanges(true);
			SlamCmsContext.Data.Cache.InvalidateTags();
		}

		[WebInvoke(Method = "GET",
		ResponseFormat = WebMessageFormat.Json,
		UriTemplate = "GetExistingTagNames?searchTerm={searchTerm}",
		BodyStyle = WebMessageBodyStyle.Bare)]
		[OperationContract]
		public List<AutoCompleteItem> GetExistingTagNames(string searchTerm)
		{
			Entities context = new Entities(ConnectionString);
			context.SetStoreSchema(ModelName, DataSchema);

			var tags = context.Tags.Where(t => t.Identifier.ToUpper().Contains(searchTerm.ToUpper()) || t.DisplayName.ToUpper().Contains(searchTerm.ToUpper()));
			return tags.Select(t => new AutoCompleteItem 
			{
				id = String.IsNullOrEmpty(t.DisplayName) ? t.Identifier : (t.DisplayName == t.Identifier ? t.DisplayName : (t.DisplayName + " (" + t.Identifier + ")")),
				value = String.IsNullOrEmpty(t.DisplayName) ? t.Identifier : (t.DisplayName == t.Identifier ? t.DisplayName : (t.DisplayName + " (" + t.Identifier + ")"))
			}).ToList();
		}
	}

	public class AutoCompleteItem
	{
		public string id
		{
			get;
			set;
		}

		public string value
		{
			get;
			set;
		}

		public string info
		{
			get;
			set;
		}
	}
}
